// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2008 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>

#define LONG_IS_INT 1

static int hexval(char c) {
  if (c >= '0' && c <= '9')
    return c - '0';
  else if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  else if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;

  return 0;
}

int atoi(const char *num) {
#if !LONG_IS_INT
  // XXX fail
#else
  return atol(num);
#endif
}

unsigned int atoui(const char *num) {
#if !LONG_IS_INT
  // XXX fail
#else
  return atoul(num);
#endif
}

long atol(const char *num) {
  long value = 0;
  int neg = 0;

  if (num[0] == '0' && num[1] == 'x') {
    // hex
    num += 2;
    while (*num && isxdigit(*num))
      value = value * 16 + hexval(*num++);
  } else {
    // decimal
    if (num[0] == '-') {
      neg = 1;
      num++;
    }
    while (*num && isdigit(*num))
      value = value * 10 + *num++ - '0';
  }

  if (neg)
    value = -value;

  return value;
}

unsigned long atoul(const char *num) {
  unsigned long value = 0;
  if (num[0] == '0' && num[1] == 'x') {
    // hex
    num += 2;
    while (*num && isxdigit(*num))
      value = value * 16 + hexval(*num++);
  } else {
    // decimal
    while (*num && isdigit(*num))
      value = value * 10 + *num++ - '0';
  }

  return value;
}

unsigned long long atoull(const char *num) {
  unsigned long long value = 0;
  if (num[0] == '0' && num[1] == 'x') {
    // hex
    num += 2;
    while (*num && isxdigit(*num))
      value = value * 16 + hexval(*num++);
  } else {
    // decimal
    while (*num && isdigit(*num))
      value = value * 10 + *num++ - '0';
  }

  return value;
}

unsigned long strtoul(const char *nptr, char **endptr, int base) {
  int neg = 0;
  unsigned long ret = 0;

  if (base < 0 || base == 1 || base > 36) {
    errno = EINVAL;
    return 0;
  }

  while (isspace(*nptr)) {
    nptr++;
  }

  if (*nptr == '+') {
    nptr++;
  } else if (*nptr == '-') {
    neg = 1;
    nptr++;
  }

  if ((base == 0 || base == 16) && nptr[0] == '0' && nptr[1] == 'x') {
    base = 16;
    nptr += 2;
  } else if (base == 0 && nptr[0] == '0') {
    base = 8;
    nptr++;
  } else if (base == 0) {
    base = 10;
  }

  for (;;) {
    char c = *nptr;
    int v = -1;
    unsigned long new_ret;

    if (c >= 'A' && c <= 'Z') {
      v = c - 'A' + 10;
    } else if (c >= 'a' && c <= 'z') {
      v = c - 'a' + 10;
    } else if (c >= '0' && c <= '9') {
      v = c - '0';
    }

    if (v < 0 || v >= base) {
      if (endptr) {
        *endptr = (char *)nptr;
      }
      break;
    }

    new_ret = ret * base;
    if (new_ret / base != ret || new_ret + v < new_ret || ret == ULONG_MAX) {
      ret = ULONG_MAX;
      errno = ERANGE;
    } else {
      ret = new_ret + v;
    }

    nptr++;
  }

  if (neg && ret != ULONG_MAX) {
    ret = -ret;
  }

  return ret;
}
